package com.example.bianYiYuanLi;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Arrays;

/**
 * @author shishuqian
 * date 2021/4/22
 * time 16:31
 *
 * 编译原理:词法分析器
 *
 * ******功能描述******
 * 1)	从文件中读入c语言源程序
 * 2)	统计行数和列数用于错误单词的定位
 * 3)	删除空格类字符，包括回车、制表符空格
 * 4)	按拼写单词，并用（单词种类，属性）二元式表示
 * 5)	如果发现错误则报告出错
 * 6)	根据需要是否填写标识符表供以后各阶段使用
 * 7)	识别注释
 * 8)	可识别小数
 * 9)   可以识别的错误有：
 * 	    标识符不规范的命名方式，如数字开头；
 * 	    连续的算数运算符连在一起,如+-
 * 	    错误的关系运算符，如<<
 * 	    一个数字里面出现两个小数点，如3.1.4
 * 	    一些非法的输入。
 *
 * ******可以识别的单词种类******
 * 1  关键字
 * 2  标识符
 * 3  常数
 * 4  运算符
 * 5  分隔符
 **/
public class LexicalAnalyzer {

    //关键字
    private static final ArrayList<String> keyword = new ArrayList<>(Arrays.asList("main","if","int","double","for","while","do","return","break","continue","printf"));
    //标识符
    private static final ArrayList<String> identifier = new ArrayList<>();
    //常数
    private static final ArrayList<String> constant = new ArrayList<>();
    //运算符
    private static final ArrayList<String> operator = new ArrayList<>(Arrays.asList("+","-","*","/","=",">","<",">=","<=","!="));
    //分隔符
    private static final ArrayList<String> separator = new ArrayList<>(Arrays.asList(",",";","(",")","{","}"));
    //行
    private static int row = 1;
    //列
    private static int column = 1;

    //主函数
    public static void main(String[] args) {

        System.out.printf("%-10s%-10s%-10s%-10s","单词","二元式","类型","位置");
        System.out.println();

        try {
            //创建随机流，可读模式
            RandomAccessFile file = new RandomAccessFile("D:\\Java\\workspaces\\Compilers\\src\\test2.txt","r");
            String strLine;
            while ((strLine = file.readLine())!= null){  //一行一行的读取
                //删除注释(单行注释)
                for (int i = 0;i < strLine.length();i++){
                    if(strLine.charAt(i)=='/' && strLine.charAt(i+1)=='/'){
                        strLine = strLine.substring(0,i);  //去掉注释部分
                        break;
                    }
                }
                analysisString(strLine);  //分析此行
                row++;       //行加1
                column = 1;  //列置1
            }
            file.close();   //关闭随机流
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("行数:"+(row-1));
//        System.out.println("标识符:"+identifier);
//        System.out.println("常量:"+constant);
    }

    //分析一行
    public static void analysisString(String str){
        //当前识别的串
        String strNow = "";
        char ch1;
        for (int i = 0;i < str.length();i++){
            ch1 = str.charAt(i); //取第i个字符
            if(ch1 == ' '){
                continue;
            }
            if((ch1>=65&&ch1<=90)||(ch1>=97&&ch1<=122)||ch1==95){ //识别字母或下划线
                strNow += ch1;
                i++;
                //超前搜索
                while (i < str.length()){
                    ch1 = str.charAt(i);
                    if((ch1>=65&&ch1<=90)||(ch1>=97&&ch1<=122)||(ch1>=48&&ch1<=57)) {  //如果是字母或数字
                        strNow+=ch1;
                        i++;
                    }else{
                        break;
                    }
                }
                i--; //指针复位
                analysisWord(strNow);  //识别单词种类
                strNow = "";
                column++;

            }else if(ch1>=48&&ch1<=57){ //识别数字
                strNow += ch1;
                i++;
                //超前搜索
                //将后续如果是字母或者是数字全部加进去
                while(i < str.length()){
                    ch1 = str.charAt(i);
                    if((ch1>=65&&ch1<=90)||(ch1>=97&&ch1<=122)||(ch1>=48&&ch1<=57)||ch1=='.'){
                        strNow += ch1;
                        i++;
                    }else{
                        break;
                    }
                }
                i--;  //指针复位
                //判断数字中是否有字母，有输出为error
                int j;
                boolean errorDone = false;  //标志是否做出了出错判定
                for (j = 0;j < strNow.length();j++){
                    char ch2 = strNow.charAt(j);
                    if((ch2>=65&&ch2<=90)||(ch2>=97&&ch2<=122)) {  //若含有字母
                        System.out.printf("%-10s%-10s%-10s%-10s",strNow,"Error","Error","("+row+","+column+")");
                        System.out.println();
                        errorDone=true;
                        break;
                    }
                }

                //判断数字中有几个小数点，有两个及以上出error
                int m;
                boolean tag = false; //tag为true时，表示已经识别到一个小数点
                for (m = 0;m < strNow.length();m++){
                    char ch2 = strNow.charAt(m);
                    if (ch2 == '.' && tag){
                        if(!errorDone) { //由于上面已经输出error，下面就不用再输出
                            System.out.printf("%-10s%-10s%-10s%-10s",strNow,"Error","Error","("+row+","+column+")");
                            System.out.println();
                        }
                        errorDone=true;
                        break;

                    }else if(ch2 == '.'){  //第一个小数点识别
                        tag = true;
                    }
                }

                if(!errorDone){  //如果没有出错判定，可将单词进行分析
                    analysisWord(strNow);
                }
                strNow="";
                column++;

            }else { //识别其它字符(除单词，数字，下划线以外的)
                strNow += ch1;
                if(separator.contains(strNow)){  //如果是分隔符
                    analysisWord(strNow);
                    strNow="";
                    column++;

                }else if(operator.contains(strNow)){  //如果是运算符
                    i++;
                    //判断运算符后面是否是关系或算术运算符，是则继续添加到strNow中
                    while(i < str.length()){
                        ch1 = str.charAt(i);
                        if(operator.contains(ch1+"")){
                            strNow+=ch1;
                            i++;
                        }else{
                            break;
                        }

                    }
                    i--; //复位
                    analysisWord(strNow);
                    strNow="";
                    column++;

                }else{  //识别非法字符
                    analysisWord(strNow);
                    strNow="";
                    column++;
                }
            }
        }
    }


    //识别单词类型
    public static void analysisWord(String str){

        int i; //区分String的首符，1为字母，2为数字，3为其他
        char ch=str.charAt(0);
        if ((ch>=65&&ch<=90)||(ch>=97&&ch<=122)||ch==95){  //字母或下划线
            i = 1;
        }else if(ch>=48&&ch<=57){  //数字
            i = 2;
        }else {  //其它字符
            i = 3;
        }

        //开始查找识别
        if (i == 1){  //首符是字母或下划线，查关键字和标识符
            boolean found = keyword.contains(str);
            if(found){
                System.out.printf("%-10s%-10s%-10s%-10s",str,"(1,"+str+")","关键字","("+row+","+column+")");
                System.out.println();
            }else{  //不是关键字，查标识符
                found = identifier.contains(str);
                System.out.printf("%-10s%-10s%-10s%-10s",str,"(6,"+str+")","标识符","("+row+","+column+")");
                System.out.println();
                if (!found) { //标识符里没有，插入
                    identifier.add(str);
                }
            }
        }else if(i == 2){ //首字符是数字，查常量
            boolean found = constant.contains(str);
            System.out.printf("%-10s%-10s%-10s%-10s",str,"(5,"+str+")","常数","("+row+","+column+")");
            System.out.println();
            if (!found) { //不在常数表中，插入
                constant.add(str);
            }
        }else{  //查分隔符，运算符
            if (separator.contains(str)){
                System.out.printf("%-10s%-10s%-10s%-10s",str,"(2,"+str+")","分界符","("+row+","+column+")");
                System.out.println();
            } else if (operator.contains(str)) {
                System.out.printf("%-10s%-10s%-10s%-10s",str,"(3,"+str+")","运算符","("+row+","+column+")");
                System.out.println();
            }else {
                System.out.printf("%-10s%-10s%-10s%-10s",str,"Error","Error","("+row+","+column+")");
                System.out.println();
            }

        }
    }

}
